package main

import (
	"fmt"
	"net"
)

/*
主go程：创建监听socket，for循环accept获取到客户端的conn，开启goroutine HandlerConnect
HandlerConnect:创建用户结构体，存入onlineMap，发送用户登录广播
Manager:监听全局的channel message，将读到的消息广播给onlineMap中的所有用户
WriteMsgToClient:读取每个用户自带channel C上消息，回写给用户
*/
// 创建用户结构体类型
type Client struct {
	C    chan string
	Name string
	Addr string
}

// 创建全局map，将用户存储到这里
var onlineMap map[string]Client

// 创建全局channel穿递用户消息
var message = make(chan string)

func WriteMsgToClient(clnt Client, conn net.Conn) {
	// 监听用户自带Channel上是否有消息
	for msg := range clnt.C {
		conn.Write([]byte(msg + "\n"))
	}
}

func Manager() {
	// 初始化map,onlineMap
	onlineMap = make(map[string]Client)
	for { // 循环从message中读取是否有数据

		// 监听channel中是否有数据,有数据存储至message,无数据就阻塞
		msg := <-message

		// 循环发送消息给所有在线用户
		for _, clnt := range onlineMap {
			clnt.C <- msg
		}
	}

}

func MakeMsg(clnt Client, msg string) (buf string) {
	buf = "[" + clnt.Addr + "]" + clnt.Name + ":" + msg
	return
}

func HandlerConnect(conn net.Conn) {
	defer conn.Close()
	// 获取用户网络地址
	netAddr := conn.RemoteAddr().String()
	// 创建新连接用户的结构体
	clnt := Client{make(chan string), netAddr, netAddr}

	// 将新连接用户添加到在线用户map中，key：IP+port value：client
	onlineMap[netAddr] = clnt

	// 创建专门用来给当前用户发送消息的goroutine
	go WriteMsgToClient(clnt, conn)

	// 发送用户上线消息到全局通道中
	//message <- "[" + netAddr + "]" + clnt.Name + "login"
	message <- MakeMsg(clnt, "login")

	// 创建一个匿名goroutine，专门处理用户发送的消息
	go func() {
		buf := make([]byte, 4096)
		for {
			n, err := conn.Read(buf)
			if n == 0 {
				fmt.Printf("检测到客户端%s退出\n", clnt.Name)
				return
			}
			if err != nil {
				fmt.Println("conn Read err", err)
				return
			}
			// 将读到的用户消息保存到msg中，string类型
			msg := string(buf[:n])

			// 将读到的用户消息广播给所用在线用户(写入到message中)
			message <- MakeMsg(clnt, msg)
		}

	}()

	// 保证不退出
	for {

	}
}

func main() {
	// 创建监听套接字
	listener, err := net.Listen("tcp", "127.0.0.1:9001")
	if err != nil {
		fmt.Println("Listen err", err)
		return
	}
	defer listener.Close()

	// 创建管理者go程
	go Manager()
	// 循环监听客户端请求
	for {
		conn, err := listener.Accept()
		if err != nil {
			fmt.Println("accept err", err)
			return
		}
		go HandlerConnect(conn)
	}
}
